<template>
	<scroll-view
		class="media-view"
		scroll-y
		@scrolltolower="loadMore"
	>
		<unicloud-db
			ref="mediaUdb"
			v-slot:default="{data, loading, error, pagination}"
			:collection="collection"
			orderby="createDate desc"
			loadtime="manual"
			:page-size="50"
			@load="onMediaListLoad"
		>
			<view v-if="(loading || processing) && pagination.current === 1" class="loading">
				<uni-icons class="icon" type="spinner-cycle" size="30" color="#000"></uni-icons>
			</view>
			<view class="items" v-else-if="mediaList.length">
				<view
					class="media-item"
					:class="{active: mediaItem.active, selected: mediaItem.selected}"
					v-for="(mediaItem, index) in mediaList"
					@click="onSelect(index)"
					:key="mediaItem._id"
				>
					<view class="image" v-if="mediaItem.type === 'image'">
						<image
							:src="mediaItem.isUploading ? mediaItem.src: coverImage(mediaItem.cover, mediaItem.thumbRules && mediaItem.thumbRules.listCover)"
							mode="aspectFill" class="img"></image>
					</view>
					<view class="image" v-if="mediaItem.type === 'video'">
						<video
							class="v"
							:src="mediaItem.src"
							:controls="false"
							:show-center-play-btn="false"
							:poster="coverImage(mediaItem.cover, mediaItem.thumbRules && mediaItem.thumbRules.listCover)"
							v-if="mediaItem.isUploading || /^cloud:\/\//.test(mediaItem._src)"
						></video>
						<image v-else :src="coverImage(mediaItem.cover, mediaItem.thumbRules && mediaItem.thumbRules.listCover)" mode="aspectFill"
						       class="img"></image>
					</view>
					<view class="mask" v-if="mediaItem.isUploading">
						<view class="progress">
							<view class="inner" :style="{width: mediaItem.percent + '%'}"></view>
						</view>
						<view :class="{tip: true, error: mediaItem.status === 'failed'}">{{ mediaItem.tip }}</view>
					</view>
				</view>
			</view>
			<view class="media-library-isnull" v-else>
				<uni-icons type="images" size="60" color="#ccc"></uni-icons>
				<view class="text">媒体库资源为空，是否上传资源？</view>
				<button
					type="primary"
					size="mini"
					@click="$emit('onUploadMedia');"
				>上传媒体资源
				</button>
			</view>
		</unicloud-db>

	</scroll-view>
</template>

<script>
import {generateCoverURL} from "../../common/crop";

const db = uniCloud.database()

export default {
	name: "media-list",
	emits: ['onSelect', 'onUploadMedia'],
	expose: ['cancelAllSelected', 'deleteSelectedMedia', 'chooseFiles', 'mediaUploadProgress', 'mediaUploadSuccess', 'mediaUploadFailed'],
	props: {
		mediaType: {
			type: String,
			default: () => "all"
		},
		mediaGroup: String,
		keyword: String,
		selectedCount: Number
	},
	data() {
		return {
			lastId: null,
			lastSelectedIndex: 0,
			remoteMediaList: [],
			localMediaList: [],
			shiftKey: false,
			isUploading: false,
			processing: true
		}
	},
	watch: {
		mediaGroup() {
			this.loadMediaList(true)
		},
		keyword() {
			this.loadMediaList(true)
		}
	},
	computed: {
		collection() {
			return [
				db.collection('uni-media-library').where(this.mediaListWhere || {}).getTemp(),
				db.collection('uni-id-users').field('_id, nickname').getTemp()
			]
		},
		mediaList() {
			return this.localMediaList.concat(this.remoteMediaList)
		},
		mediaListWhere() {
			const cond = []

			if (this.mediaType !== 'all') {
				cond.push(`type=="${this.mediaType}"`)
			}
			if (this.mediaGroup) {
				cond.push(`group=="${this.mediaGroup}"`)
			}

			if (this.keyword) {
				cond.push(`${new RegExp(this.keyword, 'i')}.test(description)`)
			}

			return cond.join('&')
		}
	},
	mounted() {
		this.loadMediaList()
		// #ifdef H5
		document.addEventListener('keydown', this.onKeyDown)
		document.addEventListener('keyup', this.onKeyUp)
		// #endif
	},
	destroyed() {
		// #ifdef H5
		document.removeEventListener('keydown', this.onKeyDown)
		document.removeEventListener('keyup', this.onKeyUp)
		// #endif
	},
	methods: {
		coverImage(url, rule) {
			return generateCoverURL(url, rule)
		},
		onKeyDown(e) {
			if (e.keyCode === 16) {
				this.shiftKey = true
			}
		},
		onKeyUp(e) {
			if (e.keyCode === 16) {
				this.shiftKey = false
			}
		},
		async onMediaListLoad(data) {
			const cloudFiles = []

			// 保存原始图片
			data.forEach(item => {
				item._src = item.src
				item._cover = item.cover
			})

			for (let i = 0; i < data.length; i++) {
				const media = data[i]
				if (media.src?.startsWith('cloud://')) {
					cloudFiles.push({
						index: i,
						type: media.type,
						src: media.src,
						cover: media.cover,
					})
				}
			}

			if (cloudFiles.length) {
				const res = await this.getMediaAssetTempURLs(
					new Set(
						[].concat.call([], ...cloudFiles.map(item => [item.src, item.cover])).filter(item => item)
					)
				)

				res.fileList.forEach(item => {
					const srcTempFile = cloudFiles.find(file => file.src === item.fileID)
					const coverTempFile = cloudFiles.find(file => file.cover === item.fileID)

					if (srcTempFile) {
						data[srcTempFile.index].src = item.tempFileURL
					}
					if (coverTempFile) {
						data[coverTempFile.index].cover = item.tempFileURL
					}
				})
			}

			this.remoteMediaList = this.remoteMediaList.concat(data)
			this.processing = false
		},
		async getMediaAssetTempURLs(listSet) {
			const limit = 30

			const fileList = Array.from(listSet)

			const res = {
				fileList: []
			}

			for (let i = 0; i < fileList.length; i += limit) {
				const resTemp = await uniCloud.getTempFileURL({
					fileList: fileList.slice(i, i + limit)
				})
				res.fileList = res.fileList.concat(resTemp.fileList)
			}

			return res
		},
		loadMediaList(reset = false) {
			// 重新加载媒体库列表清空上传列表
			if (reset) {
				this.localMediaList = []
				this.cancelAllSelected()
			}

			this.processing = true
			this.remoteMediaList = []
			this.$nextTick(() => this.$refs.mediaUdb.loadData({
				clear: reset
			}))
		},
		loadMore() {
			this.$refs.mediaUdb.loadMore()
		},
		onSelect(index) {
			if (this.isUploading) return

			let minIndex = index
			let maxIndex = index
			// shift 多选
			if (this.shiftKey) {
				minIndex = Math.min(this.lastSelectedIndex, index)
				maxIndex = Math.max(this.lastSelectedIndex, index)
			}

			const mediaItem = this.mediaList[index]

			// 如果设置了最大选择数量，判断是否超过最大数量
			if (this.selectedCount > 0 && !mediaItem.selected && !mediaItem.active) {
				const selectedMediaCount = this.mediaList.reduce((count, item) => item.selected || item.active ? count + 1 : count, 0)
				if (selectedMediaCount + (maxIndex - minIndex + 1) > this.selectedCount) {
					uni.showToast({
						title: `最多只能选择${this.selectedCount}个媒体文件`,
						icon: 'none'
					})
					return
				}
			}

			// 选择
			if (!mediaItem.selected) {
				// 选中当前
				for (let i = minIndex; i <= maxIndex; i++) {
					this.$set(this.mediaList[i], 'active', !mediaItem.active)
				}

				// 取消其他当前选中并设置为已选择
				for (const otherMediaItem of this.mediaList) {
					if (otherMediaItem.active && otherMediaItem._id !== mediaItem._id) {
						this.$set(otherMediaItem, 'active', false)
						this.$set(otherMediaItem, 'selected', true)
					}
				}
			} else {
				// 取消选择
				this.$set(this.mediaList[index], 'selected', false)
			}

			this.lastSelectedIndex = index

			this.$nextTick(() => {
				this.$emit('onSelect', this.mediaList.filter(item => item.selected || item.active))
			})
		},
		cancelAllSelected() {
			if (this.isUploading) return

			for (const mediaItem of this.mediaList) {
				if (mediaItem.active || mediaItem.selected) {
					this.$set(mediaItem, 'active', false)
					this.$set(mediaItem, 'selected', false)
				}
			}
			this.$nextTick(() => {
				this.$emit('onSelect', [])
			})
		},
		deleteSelectedMedia(mediaIds) {
			if (this.isUploading) return

			for (const id of mediaIds) {
				const index = this.remoteMediaList.findIndex(item => item._id === id)
				this.remoteMediaList.splice(index, 1)
			}

			this.$nextTick(() => {
				this.$emit('onSelect', [])
			})
		},
		chooseFiles(files = []) {
			if (this.isUploading) return

			this.isUploading = true
			this.localMediaList = files.map(file => {
				return {
					...file,
					src: file.thumbUrl,
					type: file.mediaType,
					isUploading: true,
					tip: '等待上传'
				}
			})
		},
		mediaUploadProgress(file) {
			const mediaItemIndex = this.localMediaList.findIndex(item => item.originUrl === file.originUrl)

			if (mediaItemIndex < 0) return

			this.localMediaList[mediaItemIndex].tip = '正在上传'
			this.localMediaList[mediaItemIndex].status = file.status
			this.localMediaList[mediaItemIndex].percent = file.percent
		},
		async mediaUploadSuccess(files) {
			const failedFiles = files.filter(file => file.status !== 'uploaded')

			if (failedFiles.length) return

			for (const file of files) {
				const mediaItemIndex = this.localMediaList.findIndex(item => item.originUrl === file.originUrl)
				const mediaItem = this.localMediaList[mediaItemIndex]

				if (mediaItemIndex < 0) continue

				const attrs = {
					cover: file.url, // 媒体资源封面
					...(file.attributes || {})
				}

				if (mediaItem.type === 'video') {
					// 截取视频封面
					await this.cropVideoCover(mediaItemIndex, file.url, attrs)
				}

				// 上报媒体库
				await this.reportFunction(mediaItemIndex, file.url, attrs)

				this.localMediaList[mediaItemIndex].thumbUrl = file.url
				this.localMediaList[mediaItemIndex].tip = '上传成功'
				this.localMediaList[mediaItemIndex].status = file.status
			}

			this.isUploading = false

			// 上传完成重新加载媒体库
			this.loadMediaList(true)
		},
		async mediaUploadFailed (file) {
			const mediaItemIndex = this.localMediaList.findIndex(item => item.originUrl === file.originUrl)

			if (mediaItemIndex < 0) return

			this.localMediaList[mediaItemIndex].tip = `上传失败: ${file.error?.message ?? '未知错误'}`
			this.localMediaList[mediaItemIndex].status = file.status
		},
		async reportFunction(mediaIndex, url, attrs = {}) {
			const mediaItem = this.localMediaList[mediaIndex]
			if (!mediaItem) return

			this.localMediaList[mediaIndex].tip = '正在添加至媒体库'

			const uniMediaLibraryCo = uniCloud.importObject('uni-media-library-co', {
				customUI: true
			})

			await uniMediaLibraryCo.report({
				src: url,
				type: mediaItem.type,
				originalName: mediaItem.name,
				fileType: mediaItem.fileType,
				size: mediaItem.size,
				resolution: {
					width: attrs.width || 0,
					height: attrs.height || 0
				},
				duration: attrs.duration || 0,
				uploadUser: uniCloud.getCurrentUserInfo().uid,
				...attrs
			})
		},
		async cropVideoCover(mediaIndex, url, attrs) {
			const mediaItem = this.localMediaList[mediaIndex]
			const isTcbCloud = url.startsWith('cloud://')
			const isAlipayCloud = url.startsWith('cloud://env-')

			if (!mediaItem || (isTcbCloud && !isAlipayCloud)) return undefined

			this.localMediaList[mediaIndex].tip = '正在截取视频封面'
			const ossProcessRule = 'video/snapshot,t_0,f_jpg,w_0,h_0,m_fast'
			let cropUrl = `${url}?x-oss-process=${ossProcessRule}`

			if (isAlipayCloud) {
				const res = await uniCloud.getTempFileURL({
					fileList: [url]
				})

				if (res.fileList && res.fileList.length) {
					url = res.fileList[0].tempFileURL
					cropUrl = `${url}&x-oss-process=${ossProcessRule}`
				}
			}

			const image = await fetch(cropUrl)
			const imageBlob = await image.blob()
			const imageFile = new File([imageBlob], 'image.jpg', {type: 'image/jpeg'})
			const imageBlobUrl = await this.file2blob(imageFile)

			const uploadRes = await uniCloud.uploadFile({
				filePath: imageBlobUrl,
				cloudPath: `uni-media-library/video-cover-${Date.now() + Math.round(Math.random() * 10000)}.jpg`,
				fileType: 'image'
			})

			attrs.cover = uploadRes.fileID
		},
		file2blob(file) {
			let url
			if (window.createObjectURL !== undefined) {
				url = window.createObjectURL(file);
			} else if (window.URL !== undefined) {
				url = window.URL.createObjectURL(file);
			} else if (window.webkitURL !== undefined) {
				url = window.webkitURL.createObjectURL(file);
			}
			return url
		}
	}
}
</script>

<style lang="scss">
.media-view {
	flex: 1;
	margin-top: 10px;
	overflow-y: auto;

	.items {
		display: flex;
		flex-wrap: wrap;
		gap: 10px;
	}
}

.loading {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 1;
	display: flex;
	align-items: center;
	justify-content: center;
	text-align: center;

	.icon {
		animation: rotate 1s linear infinite;
	}
}

.media-item {
	--row-media-item: 7;
	width: calc(100% / var(--row-media-item) - (((var(--row-media-item) - 1) * 10px) / (var(--row-media-item)) + 0.01px));
	border: #f5f5f5 solid 5px;
	border-radius: 6px;
	box-sizing: border-box;
	cursor: pointer;
	position: relative;
	overflow: hidden;
	transition: border-color .25s;

	&.selected {
		border-color: rgba(41, 121, 255, .5);
	}

	&.active {
		border-color: #2979ff;
	}

	.image {
		height: 0;
		padding-bottom: 100%;
		position: relative;

		.img, .v {
			position: absolute;
			top: 0;
			left: 0;
			width: 100%;
			height: 100%;
			border-radius: 4px;
		}
	}

	.mask {
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		z-index: 1;
		background: rgba(0, 0, 0, .5);
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;

		.tip {
			font-size: 12px;
			color: #fff;
			margin-top: 5px;
			&.error {
				color: red;
				text-align: center;
			}
		}
	}

	.progress {
		width: 80%;
		height: 4px;
		border-radius: 2px;
		background: #fff;
		position: relative;
		overflow: hidden;

		.inner {
			width: 0;
			height: 100%;
			border-radius: 2px;
			transition: width .3s;
			background: #2979ff;
		}
	}
}

.media-library-isnull {
	padding-top: 50px;
	text-align: center;

	.text {
		font-size: 18px;
		color: #ccc;
		margin-top: 5px;
		margin-bottom: 30px;
	}
}

@keyframes rotate {
	0% {
		transform: rotate(0deg);
	}

	100% {
		transform: rotate(360deg)
	}
}

@media screen and (max-width: 768px) {
	.media-item {
		--row-media-item: 3;
	}
}

@media screen and (min-width: 768px) {
	.media-item {
		--row-media-item: 5;
	}
}

@media screen and (min-width: 1360px) {
	.media-item {
		--row-media-item: 7;
	}
}

@media screen and (min-width: 1920px) {
	.media-item {
		--row-media-item: 10;
	}
}

@media screen and (min-width: 2560px) {
	.media-item {
		--row-media-item: 15;
	}
}
</style>
